package org.ccay.manage.log.service.impl;

import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Comparator;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;

import javax.inject.Named;

import org.apache.commons.lang.StringUtils;
import org.ccay.core.base.PageVO;
import org.ccay.core.exception.ApplicationException;
import org.ccay.core.log.CcayLoggerFactory;
import org.ccay.core.log.ILogger;
import org.ccay.core.util.DateUtil;
import org.ccay.core.util.PathUtil;
import org.ccay.core.util.StringUtil;
import org.ccay.log.LogSearchVO;
import org.ccay.manage.log.service.ILogSearchService;
import org.ccay.registry.util.RegistryQueryUtil;

@Named
public class LogSearchService implements ILogSearchService {
	
	private static final String LOG_SPLITTER = " ";//日志分隔符
	
	private static final String LOG_APPENDERFILENAME = "Ccay.Logs.AppenderFileName.";
	
	private static final int POS_SPACE = 23;//正常日志行分隔符首次出现的位置
	
	private static ILogger logger = CcayLoggerFactory.getLogger(LogSearchService.class);

	
	public LogSearchVO findLogInfo(int showTotalRows, long readBegin, byte readStep, PageVO pageVO, LogSearchVO logSearchVO)
			throws ApplicationException, IOException {
		LogSearchVO result = new LogSearchVO();
		
		String currentDate = DateUtil.dateToDateString(new Date(System.currentTimeMillis()));
		String inputDate = DateUtil.dateToDateString(logSearchVO.getDate());
		
		// begin add by zWX304259/5.4.18 修改log4j.xml的日志文件名，需要查询数据字典找文件名映射关系

		String appenderFileName = RegistryQueryUtil.getValue(LOG_APPENDERFILENAME.concat(logSearchVO.getAppender()));
		
		if (!logSearchVO.getAppender().equals(appenderFileName)) {
			logSearchVO.setAppender(appenderFileName);
		}
		
		// end add by zWX304259/5.4.18
		
		boolean breakFor = false;
		List<File> files = getFiles(logSearchVO, currentDate, inputDate);
		for (int i = 0; i < files.size(); i++) {
			File file = files.get(i);
			
			if (breakFor) {
				break;
			}
			
			if (!file.exists()) {
				logger.error("Log files that do not exist：".concat(file.getPath()));
				return result;
			}
			
			if (i>0) {
				if (!StringUtil.isNullOrEmpty(logSearchVO.getSearchFileName())) {
					String currentFileNameNub = file.getName().split("-")[2].substring(0, 1);
					int searchFileNameNub = 0;
					if (logSearchVO.getSearchFileName().split("-").length>2) {
						 searchFileNameNub = Integer.valueOf(logSearchVO.getSearchFileName().split("-")[2].substring(0, 1));
					}
					
					if (Integer.valueOf(currentFileNameNub)<searchFileNameNub) {
						continue;
					}
					
					if (!file.getName().equals(logSearchVO.getSearchFileName())) {
							readBegin=0;
							readStep=1;
					}
				}
			}
			String encodeType = RegistryQueryUtil.getValue("Ccay.Logs.CodeType");
			logger.debug("当前编码为：" + encodeType);
			logger.debug("当前文件：" + file.getName());
			
			//查找结果计数
			int  count= 0;
			//读取行计数
			int lines = 0;
			//单行内容
			String content;
			//取单笔日志时最后一次读取的内容
			String lastLine = "";
			//搜索结果
			String searchResult = "";
			//行读取辅助变量
			StringBuffer line = new StringBuffer();
			//单笔日志Buffer
			StringBuffer contentBuffer = new StringBuffer();
			//随机读
			RandomAccessFile reader = new RandomAccessFile(file, "r");
			try {
				//文件定位及方向设置
				if((readBegin == -1)&&(readStep == -1)){
					readBegin = reader.length() + readStep; //最后一个为null，所以前移一步。
				}
				reader.seek(readBegin);
				result.setBeginPointer(readBegin);
				do {
					// 按行取数据
					if(!StringUtils.isEmpty(lastLine)){
						content = lastLine;
						if(count==1){//首次找到时记录指针位置
							result.setEndPointer(reader.getFilePointer());
						}
					}else{
						lines++;
					    content = randomLine(reader, encodeType, reader.getFilePointer(), readStep, line);
					    if((showTotalRows==0)){//防止因总数干扰指针位置
					    	result.setEndPointer(reader.getFilePointer());
					    }
				    }
					//清除上一批信息
					lastLine = "";
					contentBuffer.delete(0, contentBuffer.length());
					//粗略判断为有效的日志行则准备比较
					if ((content != null)&&(content.indexOf(LOG_SPLITTER) == POS_SPACE)) {
						String[] splitException = splitInfo(content);
						//按日志分段标准判断是否标准日志行、及查询条件进行比较
						if ((isStandLine(splitException))&&(compare(splitException, logSearchVO))){
							contentBuffer.append(content);
							//反向查找时，先跳过已找到的行，此时指针位于行首本身占一位，1代表行尾的回车或换行之一
							if(readStep == -1){
								reader.seek(reader.getFilePointer() + content.getBytes("UTF-8").length + 1);
							}
							//继续查找异常堆栈
							do{
								lines++;
								content = randomLine(reader, encodeType, reader.getFilePointer() , (byte)1, line);
								String[] splitStack = splitInfo(content);
								if(isStandLine(splitStack)){
									lastLine = content;
									break;
								}else{
									if(content!=null){
									    contentBuffer.append(content);
									}
								}
							}while(content != null);
							//按关键字查找，找到则查找计数递增1
							if(contentBuffer.toString().indexOf(logSearchVO.getKeyWord()) >=0){
							    count++;
							    //记录第一次找到的结果
								if(count==1){
									searchResult = contentBuffer.toString();
								}
								//不取总数时找到则退出循环
								if(showTotalRows==0){
									breakFor = true;
									break;
								}
							}
						}
					}
				} while (content != null); //&&(content.length()>0) 日志中可能有空行，舍弃大于0的判断
				//位置更新
			} finally {
				reader.close();
				logger.debug("总查找行数：" + lines);//回车或换行算做一行
			}
			
			//结果赋值
			result.setTotalRows(count);
			//result.setEndPointer(readBegin);
			result.setKeyWord(logSearchVO.getKeyWord());
			result.setServerName(logSearchVO.getServerName());
			result.setInfo(searchResult);
			result.setSearchFileName(file.getName());
		}
		
		
		return result;
	}



	/** TODO 在此添加方法注释	
	 * @author chaowangbang
	 * @since 2016年8月18日
	 * @param logSearchVO
	 * @param files
	 * @param currentDate
	 * @param inputDate
	 * @return
	 */
	private List<File> getFiles(LogSearchVO logSearchVO, String currentDate, String inputDate) {
		List<File> files = new LinkedList<File>();
			
		if (currentDate.equalsIgnoreCase(inputDate)) {
			File file = new File(MessageFormat.format("{0}/{1}.log", PathUtil.getLogPath(logSearchVO.getServerName()),
					logSearchVO.getAppender()));
			files.add(file);
		}
		
		File[] tempFiles = getFilesByPathAndPrefix(PathUtil.getLogPath(logSearchVO.getServerName()),logSearchVO.getAppender()+ "-" +inputDate.replaceAll("-", "") );
		Arrays.sort(tempFiles, new LogSearchService.CompratorByLastModified());
		List<File> arrayList = new LinkedList<File>(Arrays.asList(tempFiles));  
		
		files.addAll(arrayList);
		
		return files;
	}

	
	
	/**
	 * TODO 按log4j中的ConversionPattern进行分段，返回时间、类型、线程名、日志类、日志内容
	 * @author chaowangbang
	 * @since 2014-1-1
	 * @param info 日志行
	 * @return
	 */
	private String[] splitInfo(String info){
	   String[] logPart = {"", "", "", "", ""};
	   if(info!=null){
		   String[] splitInfo = info.split(LOG_SPLITTER);
		   if(splitInfo.length >= 3){//根据log4J配置，标准日志行起码有3段。
			   logPart[0] = splitInfo[0];
			   logPart[1] = splitInfo[1];
			   for(int i=2; i<splitInfo.length;i++){
				   if (logPart[3].equals("")){
					   //根据类名的分隔符[]取3，否则循环取2
					   if(splitInfo[i].indexOf("]")>splitInfo[i].indexOf("[")){
						   logPart[3] = splitInfo[i];
					   }else{
						   logPart[2] = logPart[2].concat(splitInfo[i]);
					   }
				   }else{
					   //其余为4，不再拼接空格分拆值
					   logPart[4] = info.substring(info.indexOf(splitInfo[i]));
					   break;
				   }
			   }
		   }
	   }
	   return logPart;
	}
	
	/**
	 * TODO 查找条件比较，按时间、类型、编码、用户比较
	 * @author chaowangbang
	 * @since 2014-1-1
	 * @param split 拆分后的日志行
	 * @param logSearchVO
	 * @return
	 */
	private boolean compare(String[] split, LogSearchVO logSearchVO){
		return
		(compareTimeRange(split[0].substring(11, 16), logSearchVO.getTimeRange()))&&
		(("all".equalsIgnoreCase(logSearchVO.getType()))||(split[1].equalsIgnoreCase(logSearchVO.getType())))&&
		(split[2].indexOf(logSearchVO.getCode()) >= 0)&&
		(("".equals(logSearchVO.getUserAccount()))||(split[2].indexOf("-".concat(logSearchVO.getUserAccount()).concat("-"))) >= 0);
	}

	/**
	 * TODO 字符串转换为时间
	 * @author chaowangbang
	 * @since 2014-1-1
	 * @param times 日志时间
	 * @return
	 */
	private Calendar getTime(String times){
		final String TimeMark = ":";
		Calendar timeCal = Calendar.getInstance();
		String[] timeArray = times.split(TimeMark);
		timeCal.set(Calendar.HOUR_OF_DAY, Integer.valueOf(timeArray[0]));
		timeCal.set(Calendar.MINUTE, Integer.valueOf(timeArray[1]));
		if (timeArray.length == 3) {
			timeCal.set(Calendar.MINUTE, Integer.valueOf(timeArray[2]));
		} else {
			timeCal.set(Calendar.SECOND, 0);
		}
		return timeCal;
	}
	
	/**
	 * TODO 时点是否在时间段内判断函数
	 * @author chaowangbang
	 * @since 2014-1-1
	 * @param time  时点
	 * @param range 时段
	 * @return
	 */
	private boolean compareTimeRange(String time, String range){
		final String RangeMark = "-";
		String[] rangeArray = range.split(RangeMark);
		
		return(
		   (getTime(time).getTimeInMillis() >= getTime(rangeArray[0]).getTimeInMillis())&&
		   (getTime(time).getTimeInMillis() <= getTime(rangeArray[1]).getTimeInMillis())
		);
	}
	
   /**
    * TODO 随机读文件行
    * @author chaowangbang
    * @since 2014-1-1
    * @param reader 随机读写器
    * @param pos    开始位置
    * @param step	步长，为1向前，为-1向后
    * @return
    * @throws IOException
    */
   private String randomLine(RandomAccessFile reader, String encodeType, long pos, byte step, StringBuffer line) throws IOException{
		if ((step==-1)&&(pos <= 1)){
			return null;
		}
		
	    int temp = 0;
		//line = new StringBuffer();
		line.delete(0, line.length());
		reader.seek(pos);
		while (pos>=0){
			if(step == -1){
				reader.seek(pos);
			}
			temp = reader.read();
			pos = pos + step;
			//特殊字符处理
			if(temp == -1){
				break;
			}else if ((temp == 13)||(temp == 10)) {
				if(step==1){
					if(temp == 13){
						reader.seek(pos + step);   //跳过换行
					}
					break;
				}else if(step==-1){
					if(line.length() >0){
						reader.seek(pos - step); //与读后的pos + step抵消，保持持原位置不动
						break;
					}else{
						pos = pos + step;        //起始位置为末尾时跳过
					}
				}
			}else{
				line.append((char)temp);
			}
		}

		//结果
		if(line.length() > 0){			
			if(step == 1){
				return new String(line.toString().getBytes("ISO-8859-1"), encodeType).concat("\r\n");
			}else if(step == -1){
			    return new String(line.reverse().toString().getBytes("ISO-8859-1"), encodeType).concat("\r\n");
			}
		}

		if(temp == -1){
			return null;
		}else{
			return "\r\n"; //为空行时
		}
   }
   
   /**
    * TODO 是否标准日志行
    * @author chaowangbang
    * @since 2014-1-1
    * @param splitException 按日志标准拆分过的信息
    * @return
    */
   private boolean isStandLine(String[] splitException){
	 return  (splitException[0].length() == POS_SPACE)&&
		 (".debug.error.fatal.info.warn.".indexOf(splitException[1].toLowerCase()) >= 0) && 
		 (splitException[3] != "");
   }
   
   public static File[] getFilesByPathAndPrefix(String pathStr,  
           final String prefixStr) {  
       File path = new File(pathStr);  
       return getFilesByPathPrefix(path, prefixStr);  
   }  
	
	public static File[] getFilesByPathPrefix(File path, final String prefixStr) {  
       File[] fileArr = path.listFiles(new FilenameFilter() {  
           @Override  
           public boolean accept(File dir, String name) {  
               if ((StringUtils.isEmpty(prefixStr) || (dir.isDirectory() && name.startsWith(prefixStr)))) {  
                   return true;  
               } else {  
                   return false;  
               }  
           }  
       });  
       return fileArr;  
   }  
	
	static class CompratorByLastModified implements Comparator<File> {
		public boolean equals(Object obj) {
			return true;
		}

		@Override
		public int compare(File f1, File f2) {
			long diff = f1.lastModified() - f2.lastModified();
			if (diff > 0)
				return 1;
			else if (diff == 0)
				return 0;
			else
				return -1;
		}
	}
}
